
export default class Warehouse3D {
  constructor(containerId, options = {}) {
    this.rows = options.rows || 5;
    this.columns = options.columns || 20;
    this.levels = options.levels || 4;
    this.containerId = containerId;
    this.scene = null;
    this.camera = null;
    this.renderer = null;
    this.controls = null;
    this.shelves = [];
    this.selectedLocation = null;
    this.selectedBox = null;
    this.agvs = [];
    this.animationFrameId = null;
    this.agvPathPoints = [
      new THREE.Vector3(-22, 0, -22),
      new THREE.Vector3(-22, 0, 22),
      new THREE.Vector3(22, 0, 22),
      new THREE.Vector3(22, 0, -22)
    ];
  }

  init (locationData) {
    this.cleanup();
    this.createScene();
    this.createCamera();
    this.createRenderer();
    this.createLights();
    this.createFloor();
    this.createShelves(locationData);
    this.createAGVs();
    this.setupEventListeners();
    this.animate();
  }

  cleanup () {
    if (this.animationFrameId) {
      cancelAnimationFrame(this.animationFrameId);
      this.animationFrameId = null;
    }

    window.removeEventListener('resize', this.onWindowResize.bind(this));

    if (this.renderer) {
      this.renderer.dispose();
      const container = document.getElementById(this.containerId);
      if (container && container.contains(this.renderer.domElement)) {
        container.removeChild(this.renderer.domElement);
      }
      this.renderer = null;
    }

    if (this.controls) {
      this.controls.dispose();
      this.controls = null;
    }

    if (this.scene) {
      while (this.scene.children.length > 0) {
        const object = this.scene.children[0];
        if (object.geometry) object.geometry.dispose();
        if (object.material) {
          if (Array.isArray(object.material)) {
            object.material.forEach(material => material.dispose());
          } else {
            object.material.dispose();
          }
        }
        this.scene.remove(object);
      }
      this.scene = null;
    }

    this.shelves = [];
    this.selectedLocation = null;
    this.selectedBox = null;
    this.agvs = [];
  }

  createScene () {
    this.scene = new THREE.Scene();
    this.scene.background = new THREE.Color(0xf0f2f5);
  }

  createCamera () {
    const container = document.getElementById(this.containerId);
    if (!container) throw new Error('Container not found');

    const aspect = container.clientWidth / container.clientHeight;
    this.camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
    this.camera.position.set(0, 20, 20);
    this.camera.lookAt(0, 0, 0);
  }

  createRenderer () {
    const container = document.getElementById(this.containerId);
    this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    this.renderer.setSize(container.clientWidth, container.clientHeight);
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.shadowMap.enabled = true;
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    container.appendChild(this.renderer.domElement);
  }

  createLights () {
    // 环境光
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
    this.scene.add(ambientLight);

    // 主平行光
    const mainLight = new THREE.DirectionalLight(0xffffff, 0.8);
    mainLight.position.set(10, 10, 10);
    mainLight.castShadow = true;
    mainLight.shadow.mapSize.width = 2048;
    mainLight.shadow.mapSize.height = 2048;
    mainLight.shadow.camera.near = 0.5;
    mainLight.shadow.camera.far = 50;
    mainLight.shadow.camera.left = -25;
    mainLight.shadow.camera.right = 25;
    mainLight.shadow.camera.top = 25;
    mainLight.shadow.camera.bottom = -25;
    mainLight.shadow.bias = -0.001;
    this.scene.add(mainLight);

    // 辅助平行光
    const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);
    fillLight.position.set(-5, 8, -10);
    this.scene.add(fillLight);
  }

  createFloor () {
    // 根据行列数动态计算地板大小
    const floorSize = Math.max(this.columns, this.rows) * 3; // 3是每个货架的近似占位空间
    const floorGeometry = new THREE.PlaneGeometry(floorSize, floorSize);
    const floorMaterial = new THREE.MeshStandardMaterial({
      color: 0xe0e0e0,
      roughness: 0.8,
      metalness: 0.2
    });
    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
    floor.rotation.x = -Math.PI / 2;
    floor.receiveShadow = false;
    this.scene.add(floor);

    // 动态调整网格大小和分割数
    const gridSize = floorSize;
    const gridDivisions = Math.floor(gridSize / 2); // 每2个单位一个分割
    const gridHelper = new THREE.GridHelper(gridSize, gridDivisions);
    this.scene.add(gridHelper);
  }

  createShelves (locationData) {
    const shelfLength = 2;
    const shelfWidth = 2;
    const shelfHeight = 4;
    const rowCount = this.rows || 5;
    const rowSpacing = 6;

    for (let row = 0; row < rowCount; row++) {
      const z = -((rowCount - 1) * rowSpacing) / 2 + row * rowSpacing;
      this.createHorizontalShelfRow(-20, z, shelfLength, shelfWidth, shelfHeight, locationData, row);
    }
  }

  createHorizontalShelfRow (startX, z, shelfLength, shelfWidth, shelfHeight, locationData, rowIndex) {
    const shelfCount = this.columns || 20;
    const spacing = 0;
    const totalWidth = shelfCount * shelfLength;
    startX = -totalWidth / 2;

    for (let i = 0; i < shelfCount; i++) {
      const x = startX + i * (shelfLength + spacing);
      this.createShelfUnit(x, z, shelfLength, shelfWidth, shelfHeight, locationData, rowIndex, i);
    }
  }

  createShelfUnit (x, z, length, width, height, locationData, rowIndex, colIndex) {
    // 创建货架框架
    const frameGeometry = new THREE.BoxGeometry(0.2, height, width);
    const frameMaterial = new THREE.MeshStandardMaterial({
      color: 0x4a5568,
      roughness: 0.7,
      metalness: 0.3
    });

    // 创建左右支柱
    const leftFrame = new THREE.Mesh(frameGeometry, frameMaterial);
    const rightFrame = new THREE.Mesh(frameGeometry, frameMaterial);
    leftFrame.position.set(x - length / 2, height / 2, z);
    rightFrame.position.set(x + length / 2, height / 2, z);
    this.scene.add(leftFrame);
    this.scene.add(rightFrame);

    // 创建层板和货箱
    const shelfLevels = this.levels || 4;
    const levelHeight = height / this.levels;
    const startY = 0.6;

    for (let level = 0; level < shelfLevels; level++) {
      // 创建层板
      const shelfGeometry = new THREE.BoxGeometry(length, 0.1, width);
      const shelfMaterial = new THREE.MeshStandardMaterial({
        color: 0x4a5568,
        roughness: 0.7,
        metalness: 0.3
      });
      const shelf = new THREE.Mesh(shelfGeometry, shelfMaterial);
      shelf.position.set(x, startY + level * levelHeight, z);
      this.scene.add(shelf);

      // 库位编号
      const locationNumber = `R${rowIndex + 1}-C${colIndex + 1}-L${level + 1}`;

      // 添加库位编号标签
      this.createLocationLabel(locationNumber, x, startY + level * levelHeight + 0.05, z + width / 2 + 0.05, 0);
      this.createLocationLabel(locationNumber, x, startY + level * levelHeight + 0.05, z - width / 2 - 0.05, Math.PI);

      // 检查该货位是否有数据
      if (locationData[locationNumber]) {
        const { productName, quantity, status } = locationData[locationNumber];

        const boxWidth = 1.0;
        const boxHeight = 0.7;
        const boxDepth = 1.0;

        const boxGeometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
        const boxMaterial = new THREE.MeshStandardMaterial({
          color: this.getColorForStatus(status),
          roughness: 0.7,
          metalness: 0.1,
          map: this.createBoxTexture()
        });

        const box = new THREE.Mesh(boxGeometry, boxMaterial);
        box.position.set(x, startY + level * levelHeight + boxHeight / 2 + 0.05, z);
        box.castShadow = false;
        box.receiveShadow = false;
        box.userData = {
          type: 'box',
          productName: productName,
          location: locationNumber,
          quantity: quantity,
          status: status
        };

        this.scene.add(box);

        // 添加封条和标签
        const tapeGroup = this.addSealingTapes(box, boxWidth, boxHeight, boxDepth, x, startY + level * levelHeight + boxHeight / 2 + 0.05, z);
        this.scene.add(tapeGroup);

        const labelY = startY + level * levelHeight + boxHeight / 2 + 0.05;
        const frontLabel = this.createProductLabel(productName, x, labelY, z + boxDepth / 2 + 0.01, 0, 0.45);
        const backLabel = this.createProductLabel(productName, x, labelY, z - boxDepth / 2 - 0.01, Math.PI, 0.45);
        this.scene.add(frontLabel);
        this.scene.add(backLabel);
      }
    }

    // 创建后挡板
    const backGridGeometry = new THREE.PlaneGeometry(length, height);
    const backGridMaterial = new THREE.MeshStandardMaterial({
      color: 0x4a5568,
      wireframe: true,
      wireframeLinewidth: 2
    });
    const backGrid = new THREE.Mesh(backGridGeometry, backGridMaterial);
    backGrid.position.set(x, height / 2, z - width / 2);
    backGrid.rotation.y = Math.PI / 2;
    this.scene.add(backGrid);

    const shelfUnit = new THREE.Group();
    shelfUnit.add(leftFrame);
    shelfUnit.add(rightFrame);
    shelfUnit.position.set(x, 0, z);

    shelfUnit.userData = {
      type: 'shelf',
      id: `shelf_${rowIndex}_${colIndex}`,
      location: `R${rowIndex + 1}-C${colIndex + 1}`,
      status: '已占用'
    };

    this.shelves.push(shelfUnit);
  }

  createBoxTexture () {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.width = 256;
    canvas.height = 256;

    context.fillStyle = '#e6c66e';
    context.fillRect(0, 0, canvas.width, canvas.height);

    const gradient = context.createLinearGradient(0, 0, canvas.width, canvas.height);
    gradient.addColorStop(0, 'rgba(0,0,0,0.1)');
    gradient.addColorStop(0.2, 'rgba(0,0,0,0)');
    gradient.addColorStop(0.8, 'rgba(0,0,0,0)');
    gradient.addColorStop(1, 'rgba(0,0,0,0.1)');
    context.fillStyle = gradient;
    context.fillRect(0, 0, canvas.width, canvas.height);

    const texture = new THREE.CanvasTexture(canvas);
    texture.needsUpdate = true;
    return texture;
  }

  getColorForStatus (status) {
    switch (status) {
      case '正常': return 0x68d391;
      case '异常1': return 0xfc8181;
      case '异常2': return 0xf6ad55;
      default: return 0x68d391;
    }
  }

  createSealingTape (width, height, depth) {
    const tapeGeometry = new THREE.BoxGeometry(width, height, depth);
    const tapeMaterial = new THREE.MeshStandardMaterial({
      color: 0xf5f5f5,
      roughness: 0.5,
      metalness: 0.1,
      transparent: true,
      opacity: 0.9
    });
    return new THREE.Mesh(tapeGeometry, tapeMaterial);
  }

  addSealingTapes (box, boxWidth, boxHeight, boxDepth, x, y, z) {
    const tapeWidth = 0.03;
    const tapeThickness = 0.001;

    const tapeGroup = new THREE.Group();

    // 顶部封条
    const topTapeH = this.createSealingTape(boxWidth + tapeThickness, tapeWidth, tapeThickness);
    const topTapeV = this.createSealingTape(tapeWidth, tapeWidth, boxDepth + tapeThickness);
    topTapeH.position.set(0, boxHeight / 2 + tapeThickness / 2, 0);
    topTapeV.position.set(0, boxHeight / 2 + tapeThickness / 2, 0);
    tapeGroup.add(topTapeH);
    tapeGroup.add(topTapeV);

    // 底部封条
    const bottomTapeH = this.createSealingTape(boxWidth + tapeThickness, tapeWidth, tapeThickness);
    const bottomTapeV = this.createSealingTape(tapeWidth, tapeWidth, boxDepth + tapeThickness);
    bottomTapeH.position.set(0, -boxHeight / 2 - tapeThickness / 2, 0);
    bottomTapeV.position.set(0, -boxHeight / 2 - tapeThickness / 2, 0);
    tapeGroup.add(bottomTapeH);
    tapeGroup.add(bottomTapeV);

    // 前后封条
    const frontTape = this.createSealingTape(tapeWidth, boxHeight + tapeThickness, tapeThickness);
    const backTape = this.createSealingTape(tapeWidth, boxHeight + tapeThickness, tapeThickness);
    frontTape.position.set(0, 0, boxDepth / 2 + tapeThickness / 2);
    backTape.position.set(0, 0, -boxDepth / 2 - tapeThickness / 2);
    tapeGroup.add(frontTape);
    tapeGroup.add(backTape);

    // 左右封条
    const leftTape = this.createSealingTape(tapeThickness, boxHeight + tapeThickness, tapeWidth);
    const rightTape = this.createSealingTape(tapeThickness, boxHeight + tapeThickness, tapeWidth);
    leftTape.position.set(-boxWidth / 2 - tapeThickness / 2, 0, 0);
    rightTape.position.set(boxWidth / 2 + tapeThickness / 2, 0, 0);
    tapeGroup.add(leftTape);
    tapeGroup.add(rightTape);

    tapeGroup.position.set(x, y, z);
    return tapeGroup;
  }

  createProductLabel (text, x, y, z, rotation = 0, scale = 1) {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.width = 256;
    canvas.height = 64;

    context.fillStyle = '#ffffff';
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.strokeStyle = '#000000';
    context.lineWidth = 2;
    context.strokeRect(0, 0, canvas.width, canvas.height);

    context.font = 'bold 32px Arial';
    context.fillStyle = '#0066cc';
    context.textAlign = 'center';
    context.textBaseline = 'middle';

    if (Math.abs(rotation) === Math.PI) {
      context.save();
      context.translate(canvas.width / 2, canvas.height / 2);
      context.rotate(Math.PI);
      context.fillText(text, 0, 0);
      context.restore();
    } else {
      context.fillText(text, canvas.width / 2, canvas.height / 2);
    }

    const texture = new THREE.CanvasTexture(canvas);
    texture.needsUpdate = true;

    const material = new THREE.MeshBasicMaterial({
      map: texture,
      transparent: true,
      side: THREE.DoubleSide
    });

    const geometry = new THREE.PlaneGeometry(0.6 * scale, 0.15 * scale);
    const label = new THREE.Mesh(geometry, material);
    label.position.set(x, y, z);
    label.rotation.y = rotation;

    return label;
  }

  createLocationLabel (text, x, y, z, rotation = 0) {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.width = 128;
    canvas.height = 32;

    context.fillStyle = '#ffffff';
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.strokeStyle = '#000000';
    context.lineWidth = 2;
    context.strokeRect(0, 0, canvas.width, canvas.height);

    context.font = 'bold 24px Arial';
    context.fillStyle = '#000000';
    context.textAlign = 'center';
    context.textBaseline = 'middle';

    if (Math.abs(rotation) === Math.PI) {
      context.save();
      context.translate(canvas.width / 2, canvas.height / 2);
      context.rotate(Math.PI);
      context.fillText(text, 0, 0);
      context.restore();
    } else {
      context.fillText(text, canvas.width / 2, canvas.height / 2);
    }

    const texture = new THREE.CanvasTexture(canvas);
    texture.needsUpdate = true;

    const material = new THREE.MeshBasicMaterial({
      map: texture,
      transparent: true,
      side: THREE.DoubleSide
    });

    const geometry = new THREE.PlaneGeometry(0.8, 0.2);
    const label = new THREE.Mesh(geometry, material);
    label.position.set(x, y, z);
    label.rotation.y = rotation;
    this.scene.add(label);
  }

  createAGV (x, y, z) {
    const agv = new THREE.Group();

    // AGV底盘
    const baseGeometry = new THREE.BoxGeometry(1.2, 0.2, 1.6);
    const baseMaterial = new THREE.MeshStandardMaterial({
      color: 0x2c5282,
      metalness: 0.7,
      roughness: 0.3
    });
    const base = new THREE.Mesh(baseGeometry, baseMaterial);
    base.position.y = 0.1;
    agv.add(base);

    // AGV顶部
    const topGeometry = new THREE.BoxGeometry(1, 0.1, 1.4);
    const topMaterial = new THREE.MeshStandardMaterial({
      color: 0x4299e1,
      metalness: 0.6,
      roughness: 0.4
    });
    const top = new THREE.Mesh(topGeometry, topMaterial);
    top.position.y = 0.25;
    agv.add(top);

    // 轮子
    const wheelGeometry = new THREE.CylinderGeometry(0.15, 0.15, 0.1, 16);
    const wheelMaterial = new THREE.MeshStandardMaterial({
      color: 0x1a202c,
      metalness: 0.8,
      roughness: 0.2
    });

    const wheelPositions = [
      [-0.5, 0.15, 0.6],
      [0.5, 0.15, 0.6],
      [-0.5, 0.15, -0.6],
      [0.5, 0.15, -0.6]
    ];

    wheelPositions.forEach(pos => {
      const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
      wheel.position.set(...pos);
      wheel.rotation.z = Math.PI / 2;
      agv.add(wheel);
    });

    // 警示灯
    const lightGeometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
    const lightMaterial = new THREE.MeshStandardMaterial({
      color: 0xf6e05e,
      emissive: 0xf6e05e,
      emissiveIntensity: 0.5
    });
    const light = new THREE.Mesh(lightGeometry, lightMaterial);
    light.position.set(0, 0.35, 0);
    agv.add(light);

    agv.position.set(x, y, z);
    agv.castShadow = true;
    agv.receiveShadow = true;

    agv.userData = {
      type: 'agv',
      speed: 0.05,
      targetIndex: 0,
      lightOn: true
    };

    return agv;
  }

  createAGVs () {
    const agvStartPositions = [
      { pos: [-22, 0, -22], targetIndex: 0 },
      { pos: [-22, 0, 22], targetIndex: 1 },
      { pos: [22, 0, 22], targetIndex: 2 },
      { pos: [22, 0, -22], targetIndex: 3 }
    ];

    agvStartPositions.forEach((config, index) => {
      const agv = this.createAGV(...config.pos);
      agv.userData.targetIndex = config.targetIndex;
      agv.userData.speed = 0.05 + (index * 0.01);
      this.scene.add(agv);
      this.agvs.push(agv);
    });
  }

  updateAGVs () {
    this.agvs.forEach((agv, index) => {
      const currentTarget = this.agvPathPoints[agv.userData.targetIndex];
      const direction = new THREE.Vector3().subVectors(currentTarget, agv.position);

      if (direction.length() < 0.1) {
        agv.userData.targetIndex = (agv.userData.targetIndex + 1) % this.agvPathPoints.length;
      } else {
        direction.normalize();
        agv.position.add(direction.multiplyScalar(agv.userData.speed));
        const angle = Math.atan2(direction.x, direction.z);
        agv.rotation.y = angle;
      }

      const light = agv.children.find(child => child.material && child.material.emissive);
      if (light) {
        agv.userData.lightOn = !agv.userData.lightOn;
        light.material.emissiveIntensity = agv.userData.lightOn ? 0.5 : 0.2;
      }
    });
  }

  setupEventListeners () {
    // 添加轨道控制器
    this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
    this.controls.enableDamping = true;
    this.controls.dampingFactor = 0.05;
    this.controls.enableZoom = true;
    this.controls.minDistance = 5;
    this.controls.maxDistance = 50;
    this.controls.maxPolarAngle = Math.PI / 2;

    // 窗口大小调整
    window.addEventListener('resize', this.onWindowResize.bind(this));
    this.renderer.domElement.addEventListener('click', this.onMouseClick.bind(this));
  }

  onWindowResize () {
    const container = document.getElementById(this.containerId);
    if (!container || !this.camera || !this.renderer) return;

    this.camera.aspect = container.clientWidth / container.clientHeight;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(container.clientWidth, container.clientHeight);
  }

  onMouseClick (event) {
    if (!this.scene || !this.camera) return;

    const raycaster = new THREE.Raycaster();
    const mouse = new THREE.Vector2();
    const popup = document.getElementById('popup');

    const rect = this.renderer.domElement.getBoundingClientRect();
    mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
    mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

    raycaster.setFromCamera(mouse, this.camera);
    const intersects = raycaster.intersectObjects(this.scene.children, true);

    if (intersects.length > 0) {
      const clickedObject = intersects[0].object;

      const isBox = clickedObject.userData.type === 'box' ||
        (clickedObject.parent && clickedObject.parent.userData.type === 'box');
      const isShelf = clickedObject.userData.type === 'shelf' ||
        (clickedObject.parent && clickedObject.parent.userData.type === 'shelf');

      if (isBox || isShelf) {
        this.resetSelection();

        if (isShelf) {
          const shelfUnit = clickedObject.userData.type === 'shelf' ? clickedObject : clickedObject.parent;

          shelfUnit.children.forEach(child => {
            if (child.material && !child.material.wireframe) {
              child.material.color.setHex(0x3182ce);
            }
          });
          this.selectedLocation = shelfUnit;

          this.showPopup(shelfUnit.userData, event, shelfUnit);
        } else if (isBox) {
          const box = clickedObject.userData.type === 'box' ? clickedObject : clickedObject.parent;
          box.userData.originalColor = box.material.color.getHex();
          box.material.color.setHex(0x3182ce);
          this.selectedBox = box;

          this.showPopup(box.userData, event, box);
        }
      } else {
        if (popup) popup.classList.add('hidden');
        this.resetSelection();
      }
    } else {
      this.resetSelection();
      if (popup) popup.classList.add('hidden');
    }

    event.stopPropagation();
  }

  resetSelection () {
    if (this.selectedLocation) {
      this.selectedLocation.children.forEach(child => {
        if (child.material && !child.material.wireframe) {
          child.material.color.setHex(0x4a5568);
        }
      });
      this.selectedLocation = null;
    }

    if (this.selectedBox) {
      this.selectedBox.material.color.setHex(this.selectedBox.userData.originalColor || 0xe6c66e);
      this.selectedBox = null;
    }
  }

  updatePopupPosition () {
    if (!this.selectedBox || !this.renderer || !this.camera) return;

    const popup = document.getElementById('popup');
    if (!popup) return;

    const boxPosition = this.selectedBox.position.clone();
    boxPosition.y += 1;

    const vector = boxPosition.project(this.camera);
    const widthHalf = 0.5 * this.renderer.domElement.clientWidth;
    const heightHalf = 0.5 * this.renderer.domElement.clientHeight;

    vector.x = (vector.x * widthHalf) + widthHalf;
    vector.y = -(vector.y * heightHalf) + heightHalf;

    popup.style.transform = `translate(${vector.x}px, ${vector.y}px)`;
  }

  showPopup (data, event, object) {
    const popup = document.getElementById('popup');
    const popupContent = document.getElementById('popupContent');
    if (!popup || !popupContent) return;

    if (data.type === 'shelf') {
      popupContent.innerHTML = `
        <h3 class="text-lg">货位信息</h3>
        <p class="text-lg"><span class="font-medium">货位号:</span> ${data.location}</p>
        <p class="text-lg"><span class="font-medium">状态:</span> ${data.status}</span></p>
      `;
    } else if (data.type === 'box') {
      popupContent.innerHTML = `
        <p class="text-lg text-top">货品信息</p>
        <p class="text-lg"><span class="font-medium">货位号:&nbsp;</span> ${data.location}</p>
        <p class="text-lg"><span class="font-medium">产品名称:&nbsp;</span> ${data.productName}</p>
        <p class="text-lg"><span class="font-medium">数量:&nbsp;</span> ${data.quantity}件</p>
        <p class="text-lg"><span class="font-medium">状态:&nbsp;</span> ${data.status}</span></p>
      `;
    }

    this.selectedBox = object;
    this.updatePopupPosition();
    popup.classList.remove('hidden');
  }

  animate () {
    this.animationFrameId = requestAnimationFrame(this.animate.bind(this));

    if (this.controls) this.controls.update();
    this.updateAGVs();

    const popup = document.getElementById('popup');
    if (popup && !popup.classList.contains('hidden') && this.selectedBox) {
      this.updatePopupPosition();
    }

    if (this.renderer && this.scene && this.camera) {
      this.renderer.render(this.scene, this.camera);
    }
  }
}